// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2023-2023. All rights reserved.
 * Author: Huawei OS Kernel Lab
 * Create: Thu Aug 15 16:14:23 2023
 */

#include <linux/genalloc.h>
#include <linux/printk.h>
#include <linux/moduleloader.h>
#include <linux/set_memory.h>
#include <linux/kmemleak.h>
#include <asm/override.h>

#include <asm/sections.h>
/*
 * liblinux modules pool
 * should be placed near to ".text" section by lds
 */
#define LIBLINUX_MODULES_RSVD_SIZE      (1 << CONFIG_LIBLINUX_MODULES_RSVD_BITS)
extern unsigned char __liblinux_modules_pool[LIBLINUX_MODULES_RSVD_SIZE];

struct gen_pool *module_mem_pool;

void arch_module_vmalloc_init(void)
{
	int ret;

	ret = liblinux_pal_module_alloc_init(&__liblinux_modules_pool,
					    sizeof(__liblinux_modules_pool));
	if (ret < 0) {
		pr_warn("liblinux: module alloc init failed, err=%d\n", ret);
		goto err_out;
	}

	module_mem_pool = gen_pool_create(PAGE_SHIFT, -1);
	if (module_mem_pool == NULL) {
		ret = -ENOMEM;
		pr_warn("liblinux: module mem pool create failed, err=%d\n", ret);
		goto err_out;
	}

	ret = gen_pool_add(module_mem_pool,
			   (unsigned long)&__liblinux_modules_pool,
			   sizeof(__liblinux_modules_pool), -1);
	if (ret) {
		ret = -ENOMEM;
		pr_warn("liblinux: module mem pool init failed, err=%d\n", ret);
		goto err_out;
	}
	return;

err_out:
	return;
}

void * __override module_alloc(unsigned long size)
{
	unsigned long addr;
	int ret;

	addr = gen_pool_alloc(module_mem_pool, size);
	if (addr == 0)
		return NULL;

	ret = liblinux_pal_module_alloc_prepare((void *)addr, size);
	if (ret < 0) {
		gen_pool_free(module_mem_pool, addr, size);
		return NULL;
	}

	kmemleak_alloc((void *)addr, size, 1, GFP_KERNEL);
	return (void *)addr;
}

void module_memfree(void *module_region)
{
	unsigned long size;
	int ret;

	/* module_region may be null, see free_module func in kernel/module.c */
	if (module_region == NULL)
		return;

	kmemleak_free(module_region);

	kmemleak_free(module_region);

	ret = liblinux_pal_module_alloc_release(module_region, &size);
	if (ret < 0) {
		pr_warn("liblinux: invalid module mem %p\n", module_region);
		return;
	}

	gen_pool_free(module_mem_pool, (unsigned long)module_region, size);
}

/* kernel/extable.c */
int core_kernel_text(unsigned long addr)
{
	/* module is not in core kernel text */
	if ((addr >= (unsigned long)&__liblinux_modules_pool[0]) &&
	    (addr < ((unsigned long)&__liblinux_modules_pool[0] +
		     sizeof(__liblinux_modules_pool))))
		return 0;
	/* assume all other address are core kernel text */
	/* can only used by `patch_map()` */
	return 1;
}

int core_kernel_data(unsigned long addr)
{
	if (addr >= (unsigned long)_sdata &&
	    addr < (unsigned long)_edata)
		return 1;
	return 0;
}

int kernel_text_address(unsigned long addr)
{
	if (core_kernel_text(addr) || is_module_text_address(addr))
		return 1;

	return 0;
}
